#region File Description
//-----------------------------------------------------------------------------
// LobbyScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Net;
using System.IO;
using GrapplingHookGameData;
#endregion

namespace GrapplingHookGame
{
    /// <summary>
    /// The lobby screen provides a place for gamers to congregate before starting
    /// the actual gameplay. It displays a list of all the gamers in the session,
    /// and indicates which ones are currently talking. Each gamer can press a button
    /// to mark themselves as ready: gameplay will begin after everyone has done this.
    /// </summary>
    class LobbyScreen : MenuScreen
    {
        #region Fields

        NetworkSession networkSession;
        private PacketWriter packetWriter;
        private PacketReader packetReader;

        Texture2D isReadyTexture;
        Texture2D hasVoiceTexture;
        Texture2D isTalkingTexture;
        Texture2D voiceMutedTexture;

        ChoiceMenuEntry levelSelectMenuEntry;
        string lastCurrentLevel = "";
        ChoiceMenuEntry roundsMenuEntry;
        string lastRounds = "";

        #endregion

        #region Initialization


        /// <summary>
        /// Constructs a new lobby screen.
        /// </summary>
        public LobbyScreen(NetworkSession networkSession):base(Resources.Lobby)
        {
            this.networkSession = networkSession;

            TransitionOnTime = TimeSpan.FromSeconds(0.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);

            packetWriter = new PacketWriter();
            packetReader = new PacketReader();

            networkSession.GamerJoined += new EventHandler<GamerJoinedEventArgs>(networkSession_GamerJoined);
        }

        /// <summary>
        /// Called when a gamer joins. Makes sure the level name is sent to the new joiner.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void networkSession_GamerJoined(object sender, GamerJoinedEventArgs e)
        {
            //force a sending of level name and rounds each time a gamer joins
            if( networkSession.IsHost )
            {
                lastCurrentLevel = "";
                lastRounds = "";
            }
        }


        /// <summary>
        /// Loads graphics content used by the lobby screen.
        /// </summary>
        public override void LoadContent()
        {
            ContentManager content = ScreenManager.Game.Content;

            isReadyTexture = content.Load<Texture2D>("menu/chat_ready");
            hasVoiceTexture = content.Load<Texture2D>("menu/chat_able");
            isTalkingTexture = content.Load<Texture2D>("menu/chat_talking");
            voiceMutedTexture = content.Load<Texture2D>("menu/chat_mute");

            //find all levels
            string levelDirectory = content.RootDirectory + Path.DirectorySeparatorChar + GrapplingHookGameData.Constants.LEVEL_DIRECTORY;
//            string[] possibleLevelFiles = Directory.GetFiles(levelDirectory, "*.xnb", SearchOption.TopDirectoryOnly);
            string[] possibleLevelFiles = Directory.GetFiles(levelDirectory, "*.xnb");
            string[] levels = new string[possibleLevelFiles.Length];
            for( int i=0; i< possibleLevelFiles.Length; i++)
            {
                levels[i] = Path.GetFileNameWithoutExtension(possibleLevelFiles[i]);
            }

            //create menu entry for level
            levelSelectMenuEntry = new ChoiceMenuEntry(levels);
            levelSelectMenuEntry.Label = Resources.LevelPrefix;
            MenuEntries.Add(levelSelectMenuEntry);

            //create menu entry for rounds
            string[] r = new string[Constants.MAX_ROUNDS];
            for (int i = 0; i < Constants.MAX_ROUNDS; i++) r[i] = (i + 1).ToString();
            roundsMenuEntry = new ChoiceMenuEntry(r);
            roundsMenuEntry.Label = Resources.RoundsPrefix;
            MenuEntries.Add(roundsMenuEntry);
        }

        #endregion

        #region Update


        /// <summary>
        /// Updates the lobby screen.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

            if (!IsExiting)
            {
                if (networkSession.SessionState == NetworkSessionState.Playing)
                {
                    // Check if we should leave the lobby and begin gameplay.
                    LoadingScreen.Load(ScreenManager, true, new GameplayScreen(networkSession, lastCurrentLevel, int.Parse(lastRounds)));
                }
                else if (networkSession.IsHost && networkSession.IsEveryoneReady)
                {
                    // The host checks whether everyone has marked themselves
                    // as ready, and starts the game in response.
                    networkSession.StartGame();
                }
                else
                    SendAndRecieveLevelChanges();

            }
        }

        private void SendAndRecieveLevelChanges()
        {
            //distribute level changes if host
            if (networkSession.IsHost)
            {
                if (!levelSelectMenuEntry.SelectedText.Equals(lastCurrentLevel) || !roundsMenuEntry.SelectedText.Equals(lastRounds))
                {
                    packetWriter.Write(levelSelectMenuEntry.SelectedText);
                    packetWriter.Write(roundsMenuEntry.SelectedText);
                    LocalNetworkGamer server = (LocalNetworkGamer)networkSession.Host;
                    server.SendData(packetWriter, SendDataOptions.Reliable);
                    lastCurrentLevel = levelSelectMenuEntry.SelectedText;
                    lastRounds = roundsMenuEntry.SelectedText;
                }
            }
            //read level changes if client
            else
            {
                foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
                {
                    while (gamer.IsDataAvailable)
                    {
                        NetworkGamer sender;
                        gamer.ReceiveData(packetReader, out sender);
                        string newLevel = packetReader.ReadString();
                        string newRounds = packetReader.ReadString();
                        if (!newLevel.Equals("\0")) lastCurrentLevel = newLevel;   //change level as long as its not a null-string
                        if (!newRounds.Equals("\0")) lastRounds = newRounds;   //change level as long as its not a null-string
                    }
                }
            }
        }

        /// <summary>
        /// Handles user input for all the local gamers in the session. Unlike most
        /// screens, which use the InputState class to combine input data from all
        /// gamepads, the lobby needs to individually mark specific players as ready,
        /// so it loops over all the local gamers and reads their inputs individually.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            base.HandleInput(input);

            foreach (LocalNetworkGamer gamer in networkSession.LocalGamers)
            {
                PlayerIndex playerIndex = gamer.SignedInGamer.PlayerIndex;

                if (input.IsMenuSelect(playerIndex))
                {
                    HandleMenuSelect(gamer);
                }
                else if (input.IsMenuCancel(playerIndex))
                {
                    HandleMenuCancel(gamer);
                }
            }
        }


        /// <summary>
        /// Handle MenuSelect inputs by marking ourselves as ready.
        /// </summary>
        void HandleMenuSelect(LocalNetworkGamer gamer)
        {
            if (!gamer.IsReady)
            {
                gamer.IsReady = true;
            }
            else if (gamer.IsHost)
            {
                // The host has an option to force starting the game, even if not
                // everyone has marked themselves ready. If they press select twice
                // in a row, the first time marks the host ready, then the second
                // time we ask if they want to force start.
                MessageBoxScreen messageBox = new MessageBoxScreen(
                                                    Resources.ConfirmForceStartGame);

                messageBox.Accepted += ConfirmStartGameMessageBoxAccepted;

                ScreenManager.AddScreen(messageBox);
            }
        }


        /// <summary>
        /// Event handler for when the host selects ok on the "are you sure
        /// you want to start even though not everyone is ready" message box.
        /// </summary>
        void ConfirmStartGameMessageBoxAccepted(object sender, EventArgs e)
        {
            if (networkSession.SessionState == NetworkSessionState.Lobby)
            {
                networkSession.StartGame();
            }
        }


        /// <summary>
        /// Handle MenuCancel inputs by clearing our ready status, or if it is
        /// already clear, prompting if the user wants to leave the session.
        /// </summary>
        void HandleMenuCancel(LocalNetworkGamer gamer)
        {
            if (gamer.IsReady)
            {
                gamer.IsReady = false;
            }
            else
            {
                NetworkSessionComponent.LeaveSession(ScreenManager);
            }
        }

        /// <summary>
        /// Do not exit the lobby screen on escape, wait for message box confirmation.
        /// </summary>
        protected override void OnCancel()
        {
        }

        #endregion

        #region Draw


        /// <summary>
        /// Draws the lobby screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            //draw level select menu for host
            if (networkSession.IsHost) base.Draw(gameTime);

            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
            SpriteFont font = ScreenManager.Font;
            Vector2 playersPosition = new Vector2(100, 200);

            //put menu in bottom
            Position = new Vector2(Position.X, playersPosition.Y + ScreenManager.Font.LineSpacing * 10);

            // Make the lobby slide into place during transitions.
            float transitionOffset = (float)Math.Pow(TransitionPosition, 2);

            if (ScreenState == ScreenState.TransitionOn)
                playersPosition.X -= transitionOffset * 256;
            else
                playersPosition.X += transitionOffset * 512;

            spriteBatch.Begin();

            // Draw all the gamers in the session.
            int gamerCount = 0;

            foreach (NetworkGamer gamer in networkSession.AllGamers)
            {
                DrawGamer(gamer, playersPosition);

                // Advance to the next screen position, wrapping into two
                // columns if there are more than 8 gamers in the session.
                if (++gamerCount == 8)
                {
                    playersPosition.X += 433;
                    playersPosition.Y = 150;
                }
                else
                    playersPosition.Y += ScreenManager.Font.LineSpacing;
            }

            //draw level info if not host
            if( !networkSession.IsHost)
            {
                spriteBatch.DrawString(font, lastCurrentLevel, Position, FadeAlphaDuringTransition(Color.White));
                playersPosition.Y += ScreenManager.Font.LineSpacing;
                spriteBatch.DrawString(font, lastRounds, Position, FadeAlphaDuringTransition(Color.White));
            }

            spriteBatch.End();

        }


        /// <summary>
        /// Helper draws the gamertag and status icons for a single NetworkGamer.
        /// </summary>
        void DrawGamer(NetworkGamer gamer, Vector2 position)
        {
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
            SpriteFont font = ScreenManager.Font;

            Vector2 iconWidth = new Vector2(34, 0);
            Vector2 iconOffset = new Vector2(0, 12);

            Vector2 iconPosition = position + iconOffset;

            // Draw the "is ready" icon.
            if (gamer.IsReady)
            {
                spriteBatch.Draw(isReadyTexture, iconPosition,
                                 FadeAlphaDuringTransition(Color.Lime));
            }

            iconPosition += iconWidth;

            // Draw the "is muted", "is talking", or "has voice" icon.
            if (gamer.IsMutedByLocalUser)
            {
                spriteBatch.Draw(voiceMutedTexture, iconPosition,
                                 FadeAlphaDuringTransition(Color.Red));
            }
            else if (gamer.IsTalking)
            {
                spriteBatch.Draw(isTalkingTexture, iconPosition,
                                 FadeAlphaDuringTransition(Color.Yellow));
            }
            else if (gamer.HasVoice)
            {
                spriteBatch.Draw(hasVoiceTexture, iconPosition,
                                 FadeAlphaDuringTransition(Color.White));
            }

            // Draw the gamertag, normally in white, but yellow for local players.
            string text = gamer.Gamertag;

            if (gamer.IsHost)
                text += Resources.HostSuffix;

            Color color = (gamer.IsLocal) ? Color.Yellow : Color.White;

            spriteBatch.DrawString(font, text, position + iconWidth * 2,
                                   FadeAlphaDuringTransition(color));

        }


        #endregion
    }
}
